#ifndef _AMERICO_MATRIX44_H_
#define _AMERICO_MATRIX44_H_

#include <cmath>
#include "staticarray.h"
#include "Vector4.h"

// Matrix44 Declaration
template <typename TYPE>
class Matrix44
{
public:
	Matrix44();
	Matrix44(const Matrix44<TYPE> &aMatrix);
	~Matrix44();

	Matrix44 &operator=(const Matrix44 &aMatrix);

	bool operator==(const Matrix44 &aMatrix) const;
	bool operator!=(const Matrix44 &aMatrix) const;

	const TYPE &operator[](const unsigned int &anIndex) const;
	TYPE &operator[](const unsigned int &anIndex);

	Matrix44 operator+(const Matrix44 &aMatrix) const;
	Matrix44 operator-(const Matrix44 &aMatrix) const;
	Matrix44 operator*(const Matrix44 &aMatrix) const;
	Matrix44 operator*(const TYPE &aScalar) const;

	Vector4<TYPE> operator*(const Vector4<TYPE> &aVector) const;
	Vector3<TYPE> GetTranslate();

	Matrix44 &operator+=(const Matrix44 &aMatrix);
	Matrix44 &operator-=(const Matrix44 &aMatrix);
	Matrix44 &operator*=(const Matrix44 &aMatrix);
	Matrix44 &operator*=(const TYPE &aScalar);

	Matrix44 operator~() const;

	void Init(const TYPE someData[16]);
	void ConvertD3D(const TYPE someData[16]);

	static Matrix44 Convert(const TYPE someData[16]);
	void ConvertToRowMajor();

	static Matrix44 CreateRotateAroundX(const TYPE &anAngleInRadians);
	static Matrix44 CreateRotateAroundY(const TYPE &anAngleInRadians);
	static Matrix44 CreateRotateAroundZ(const TYPE &anAngleInRadians);
	static Matrix44 CreateRotate(const Vector3<TYPE> &aVector);

	static Matrix44 CreateTranslate(const Vector3<TYPE> &aVector);

	static Matrix44 Identity();

private:
	Utils::StaticArray<TYPE, 4*4> myMatrix;
};

// Matrix44 Definition
template <typename TYPE>
Matrix44<TYPE>::Matrix44()
{
	myMatrix[0] = myMatrix[5] = myMatrix[10] = myMatrix[15] = 1;
	myMatrix[1] = myMatrix[2] = myMatrix[3] = myMatrix[4] = 0;
	myMatrix[6] = myMatrix[7] = myMatrix[8] = myMatrix[9] = 0;
	myMatrix[11] = myMatrix[12] = myMatrix[13] = myMatrix[14] = 0;
}

template <typename TYPE>
Matrix44<TYPE>::Matrix44(const Matrix44<TYPE> &aMatrix)
{
	myMatrix = aMatrix.myMatrix;
}

template <typename TYPE>
Matrix44<TYPE>::~Matrix44()
{
}

template <typename TYPE>
Matrix44<TYPE> &Matrix44<TYPE>::operator =(const Matrix44<TYPE> &aMatrix)
{
	for(unsigned int index=0; index<16; ++index)
	{
		myMatrix[index] = aMatrix.myMatrix[index];
	}
	return *this;
}

template <typename TYPE>
bool Matrix44<TYPE>::operator ==(const Matrix44<TYPE> &aMatrix) const
{
	for(unsigned int index=0; index<16; ++index)
	{
		if(myMatrix[index] != aMatrix.myMatrix[index])
		{
			return false;
		}
	}
	return true;
}

template <typename TYPE>
bool Matrix44<TYPE>::operator !=(const Matrix44<TYPE> &aMatrix) const
{
	return !(operator ==(aMatrix));
}

template <typename TYPE>
const TYPE &Matrix44<TYPE>::operator [](const unsigned int &anIndex) const
{
	return myMatrix[anIndex];
}

template <typename TYPE>
TYPE &Matrix44<TYPE>::operator [](const unsigned int &anIndex)
{
	return myMatrix[anIndex];
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::operator +(const Matrix44<TYPE> &aMatrix) const
{
	Matrix44<TYPE> result(*this);
	result += aMatrix;
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::operator -(const Matrix44<TYPE> &aMatrix) const
{
	Matrix44<TYPE> result(*this);
	result -= aMatrix;
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::operator *(const Matrix44<TYPE> &aMatrix) const
{
	Matrix44<TYPE> result(*this);
	result *= aMatrix;
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::operator *(const TYPE &aScalar) const
{
	Matrix44<TYPE> result(*this);
	result *= aScalar;
	return result;
}

template <typename TYPE>
Vector4<TYPE> Matrix44<TYPE>::operator *(const Vector4<TYPE> &aVector) const
{
	Vector4<TYPE> result(0,0,0,1);
	result.x = aVector.myX*myMatrix[0] + aVector.myY*myMatrix[4] + aVector.myZ*myMatrix[8] + aVector.myW*myMatrix[12];
	result.y = aVector.myX*myMatrix[1] + aVector.myY*myMatrix[5] + aVector.myZ*myMatrix[9] + aVector.myW*myMatrix[13];
	result.z = aVector.myX*myMatrix[2] + aVector.myY*myMatrix[6] + aVector.myZ*myMatrix[10] + aVector.myW*myMatrix[14];
	result.w = aVector.myX*myMatrix[3] + aVector.myY*myMatrix[7] + aVector.myZ*myMatrix[11] + aVector.myW*myMatrix[15];
	return result;
}

template <typename TYPE>
Vector3<TYPE> Matrix44<TYPE>::GetTranslate()
{
	return Vector3<TYPE>( myMatrix[12], myMatrix[13], myMatrix[14] );
}

template <typename TYPE>
Matrix44<TYPE> &Matrix44<TYPE>::operator +=(const Matrix44<TYPE> &aMatrix)
{
	for(unsigned int index=0; index<16; ++index)
	{
		myMatrix[index] += aMatrix.myMatrix[index];
	}
	return *this;
}

template <typename TYPE>
Matrix44<TYPE> &Matrix44<TYPE>::operator -=(const Matrix44<TYPE> &aMatrix)
{
	for(unsigned int index=0; index<16; ++index)
	{
		myMatrix[index] -= aMatrix.myMatrix[index];
	}
	return *this;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::operator~() const
{
	Matrix44<TYPE> result;
	for(unsigned int i=0; i<3; ++i)
	{
		for(unsigned int j=0; j<3; ++j)
		{
			result.myMatrix[i + j*4] = myMatrix[j + i*4];
		}
	}
	result[12] = -myMatrix[12]*myMatrix[0] - myMatrix[13]*myMatrix[1] - myMatrix[14]*myMatrix[2];
	result[13] = -myMatrix[12]*myMatrix[4] - myMatrix[13]*myMatrix[5] - myMatrix[14]*myMatrix[6];
	result[14] = -myMatrix[12]*myMatrix[8] - myMatrix[13]*myMatrix[9] - myMatrix[14]*myMatrix[10];
	return result;
}

template <typename TYPE>
Matrix44<TYPE> &Matrix44<TYPE>::operator *=(const Matrix44<TYPE> &aMatrix)
{
	Matrix44<TYPE> matrix(*this);
	for(unsigned int i=0; i<4; ++i)
	{
		for(unsigned int j=0; j<4; ++j)
		{
			myMatrix[i + 4*j] = 0;
			for(unsigned int r=0; r<4; ++r)
			{
#ifdef MATRIX_COLUMN
				myMatrix[i + 4*j] += matrix.myMatrix[r + 4*i] * aMatrix.myMatrix[j + 4*r];
#else
				myMatrix[i + 4*j] += matrix.myMatrix[i + 4*r] * aMatrix.myMatrix[r + 4*j];
#endif
			}
		}
	}
	return *this;
}

template <typename TYPE>
Matrix44<TYPE> &Matrix44<TYPE>::operator *=(const TYPE &aScalar)
{
	for(unsigned int i=0; i<4*4; ++i)
	{
		myMatrix[i] *= aScalar;
	}
	return *this;
}

template <typename TYPE>
void Matrix44<TYPE>::Init(const TYPE someData[16])
{
	for(unsigned int index=0; index<16; ++index)
	{
		myMatrix[index] = someData[index];
	}
}

template <typename TYPE>
void Matrix44<TYPE>::ConvertD3D(const TYPE someData[16])
{
	Init(someData);
	//for(unsigned int i=0; i<4; ++i)
	//{
	//	for(unsigned int j=0; j<4; ++j)
	//	{
	//		myMatrix[i+4*j] = someData[4*i+j];
	//	}
	//}
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::Convert(const TYPE someData[])
{
	Matrix44<TYPE> ret;
	ret.Init(someData);
	return ret;
}

template <typename TYPE>
void Matrix44<TYPE>::ConvertToRowMajor()
{
#define swapMatrix(a, b) { TYPE temp = myMatrix[a]; myMatrix[a] = myMatrix[b]; myMatrix[b] = temp; }
	swapMatrix(1,4);
	swapMatrix(2,8);
	swapMatrix(3,12);
	swapMatrix(6,9);
	swapMatrix(7,13);
	swapMatrix(11,14);
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::CreateRotateAroundX(const TYPE &anAngleInRadians)
{
	Matrix44<TYPE> result;
	result.myMatrix[5] = result.myMatrix[10] = cos(anAngleInRadians);
	result.myMatrix[9] = sin(anAngleInRadians);
	result.myMatrix[6] = -result.myMatrix[9];
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::CreateRotateAroundY(const TYPE &anAngleInRadians)
{
	Matrix44<TYPE> result;
	result.myMatrix[0] = result.myMatrix[10] = cos(anAngleInRadians);
	result.myMatrix[2] = sin(anAngleInRadians);
	result.myMatrix[8] = -result.myMatrix[2];
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::CreateRotateAroundZ(const TYPE &anAngleInRadians)
{
	Matrix44<TYPE> result;
	result.myMatrix[0] = result.myMatrix[5] = cos(anAngleInRadians);
	result.myMatrix[4] = sin(anAngleInRadians);
	result.myMatrix[1] = -result.myMatrix[4];
	return result;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::CreateRotate(const Vector3<TYPE> &aVector)
{
	return CreateRotateAroundX(aVector.x) * CreateRotateAroundY(aVector.y) * CreateRotateAroundZ(aVector.z);
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::CreateTranslate(const Vector3<TYPE> &aVector)
{
	Matrix44<TYPE> translate;
	translate[12] = aVector.x;
	translate[13] = aVector.y;
	translate[14] = aVector.z;
	return translate;
}

template <typename TYPE>
Matrix44<TYPE> Matrix44<TYPE>::Identity()
{
	return Matrix44<TYPE>();
}
#endif
